package com.xxxx.file.controller.admin;

import com.alibaba.fastjson.JSON;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.vod.model.v20170321.GetMezzanineInfoResponse;
import com.xxxx.server.dto.FileDto;
import com.xxxx.server.dto.ResponseDto;
import com.xxxx.server.enums.FileUseEnum;
import com.xxxx.server.service.FileService;
import com.xxxx.server.util.Base64ToMultipartFile;
import com.xxxx.server.util.VodUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.*;

/**
 * Created by stfu
 * 2021-08-13 18:10
 */
@RestController
@RequestMapping("/admin")
public class UploadController {

    public static final String BUSINESS_NAME = "文件上传";

    private static final Logger LOG = LoggerFactory.getLogger(UploadController.class);

    @Value("${file.domain}")
    private String FILE_DOMAIN;

    @Value("${file.path}")
    private String FILE_PATH;

    @Value("${oss.ossDomain}")
    private String ossDomain;

    @Value("${vod.accessKeyId}")
    private String accessKeyId;

    @Value("${vod.accessKeySecret}")
    private String accessKeySecret;

    @Resource
    private FileService fileService;

    /* 对应formData格式的参数传递
    @PostMapping("/upload")
    public ResponseDto upload(@RequestParam MultipartFile shard,  // MultipartFile是spring类型，代表HTML中form data方式上传的文件，包含二进制数据+文件名称
                              String use,
                              String name,
                              String suffix,
                              Integer size,
                              Integer shardIndex,
                              Integer shardSize,
                              Integer shardTotal,
                              String key) throws IOException {
    */
    /**
     * 分片上传(保存在本地)
     * @param fileDto
     * @return
     * @throws IOException
     */
    @PostMapping("/upload")
    public ResponseDto upload(@RequestBody FileDto fileDto) throws IOException {
        ResponseDto<Object> responseDto = new ResponseDto<>();
        String use = fileDto.getUse();
        String key = fileDto.getKey();
        String suffix = fileDto.getSuffix();
        String shardBase64 = fileDto.getShard();
        MultipartFile shard = Base64ToMultipartFile.base64ToMultipart(shardBase64);

        //  保存文件到本地
        FileUseEnum useEnum = FileUseEnum.getByCode(use);
        // 如果文件夹不存在则创建
        String dir = useEnum.name().toLowerCase();
        File fullDir = new File(FILE_PATH + dir);   //  D:/file/MyProjects/course/course
        if (!fullDir.exists()) {
            fullDir.mkdir();
        }

        // 因为在不同系统下, 文件分隔符的要求不一样, 所以用 File.separator 保证了在任何系统下不会出错。
        // String path = dir + File.separator + key + "." + suffix + "." + fileDto.getShardIndex(); //  加上key防止文件重名
        String path = new StringBuffer(dir)  // 一般太长的拼接就用StringBuffer()
                .append(File.separator)
                .append(key).append(".")
                .append(suffix)
                .toString();    //  course/6sfSqfOwzmik4A4icMYuUe.mp4
        String localPath = new StringBuffer(path)
                .append(".")
                .append(fileDto.getShardIndex())
                .toString();    //  course/6sfSqfOwzmik4A4icMYuUe.mp4.1
        String fullPath = FILE_PATH + localPath;    //  D:/file/MyProjects/course/course/6sfSqfOwzmik4A4icMYuUe.mp4.1

        File dest = new File(fullPath); //  生成保存路径
        shard.transferTo(dest);
        LOG.info(dest.getAbsolutePath());

        //  保存到数据库和返回给前端的路径是不含有 .1 的path
        LOG.info("保存文件记录开始");
        fileDto.setPath(path);
        fileService.save(fileDto);

        //  返回资源的全路径
        fileDto.setPath(FILE_DOMAIN + path);    //  http://127.0.0.1:9000/file/f/course/6sfSqfOwzmik4A4icMYuUe.mp4
        responseDto.setContent(fileDto);

        if (fileDto.getShardIndex().equals(fileDto.getShardTotal())) {
            this.merge(fileDto);    //  合并分片
        }

        return responseDto;
    }

    /**
     * 合并分片
     * @param fileDto
     * @throws FileNotFoundException
     */
    private void merge(FileDto fileDto) throws FileNotFoundException {
        LOG.info("合并分片开始");
        String path = fileDto.getPath();    //  这个path是全路径, http://127.0.0.1:9000/file/f/course/6sfSqfOwzmik4A4icMYuUe.mp4
        path = path.replace(FILE_DOMAIN, "");   //  course/6sfSqfOwzmik4A4icMYuUe.mp4
        File newFile = new File(FILE_PATH + path);
        FileOutputStream outputStream = new FileOutputStream(newFile, true);    // true表示文件是追加写入
        FileInputStream fileInputStream = null;     //分片文件
        byte[] byt = new byte[1 * 1024 * 1024];
        int len;

        try {
            for (int i = 0; i < fileDto.getShardTotal(); i++) {
                //  读取第i个分片
                fileInputStream = new FileInputStream(new File(FILE_PATH + path + "." + (i + 1)));  // course/6sfSqfOwzmik4A4icMYuUe.mp4.i
                while ((len = fileInputStream.read(byt)) != -1) {
                    outputStream.write(byt, 0, len);
                }
            }
        } catch (IOException e) {
            LOG.error("分片合并异常", e);
        } finally {
            try {
                if (fileInputStream != null) {
                    fileInputStream.close();
                }
                outputStream.close();
                LOG.info("IO流关闭");
            } catch (Exception e) {
                LOG.error("IO流关闭", e);
            }
        }
        LOG.info("合并分片结束");

        //  调用一次垃圾回收, 就算IO流关闭了, 程序还是会占有一些文件的信息, 会导致删除分片失败
        System.gc();
        try {
            Thread.sleep(100);  // 不加上这个会有一些分片还是删除失败, 因为调用gc时, 虚拟机并不会立马执行该操作
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //  删除分片
        LOG.info("删除分片开始");
        for (int i = 0; i < fileDto.getShardTotal(); i++) {
            String filePath = FILE_PATH + path + "." + (i + 1);
            File file = new File(filePath);
            boolean result = file.delete();
            LOG.info("删除{}, {}", filePath, result ? "成功" : "失败");
        }
        LOG.info("删除分片结束");
    }

    /**
     * 检查文件
     * @param key
     * @return
     * @throws Exception
     */
    @GetMapping("/check/{key}")
    public ResponseDto check(@PathVariable String key) throws Exception {
        LOG.info("检查上传分片开始: {}", key);
        ResponseDto<FileDto> responseDto = new ResponseDto<>();

        FileDto fileDto = fileService.findByKey(key);
        if (fileDto != null) {
            if (StringUtils.isEmpty(fileDto.getVod())) {    // 如果vod字段为空, 则走oss
                fileDto.setPath(ossDomain + fileDto.getPath());
            } else {    // 否则走vod
                DefaultAcsClient vodClient = VodUtil.initVodClient(accessKeyId, accessKeySecret);
                GetMezzanineInfoResponse response = VodUtil.getMezzanineInfo(vodClient, fileDto.getVod());
                System.out.println("获取视频信息, response : " + JSON.toJSONString(response));
                String fileUrl = response.getMezzanine().getFileURL();
                fileDto.setPath(fileUrl);
            }
        }
        responseDto.setContent(fileDto);
        return responseDto;
    }

}
